/**
 * 
 */
package javapop.framework.generic.tool;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javapop.framework.DefaultParseContext;
import javapop.framework.Grammar;
import javapop.framework.ParseContext;
import javapop.framework.ParseError;
import javapop.framework.ParseInput;
import javapop.framework.ParseResult;
import javapop.framework.Parser;
import javapop.framework.generic.GChoice;
import javapop.framework.generic.GDefRegle;
import javapop.framework.generic.GExprBracket;
import javapop.framework.generic.GExprOperand;
import javapop.framework.generic.GExprOperator;
import javapop.framework.generic.GExprOperator.Assoc;
import javapop.framework.generic.GExprParser;
import javapop.framework.generic.GIgnore;
import javapop.framework.generic.GKeyword;
import javapop.framework.generic.GKeywords;
import javapop.framework.generic.GListOf;
import javapop.framework.generic.GLiteral;
import javapop.framework.generic.GMaybe;
import javapop.framework.generic.GMaybeOrDefault;
import javapop.framework.generic.GMeta;
import javapop.framework.generic.GParser;
import javapop.framework.generic.GRefParser;
import javapop.framework.generic.GRegexp;
import javapop.framework.generic.GRegexpWithoutKeywords;
import javapop.framework.generic.GRegle;
import javapop.framework.generic.GTransform;
import javapop.framework.generic.GTuple;
import javapop.framework.generic.GenericGrammar;
import javapop.framework.generic.GrammarTree;
import javapop.framework.input.StringParseInput;
import javapop.framework.parser.Choice;
import javapop.framework.parser.Forget;
import javapop.framework.parser.ForgetAll;
import javapop.framework.parser.LineComment;
import javapop.framework.parser.ListOf;
import javapop.framework.parser.Literal;
import javapop.framework.parser.Many;
import javapop.framework.parser.Maybe;
import javapop.framework.parser.MaybeOrDefault;
import javapop.framework.parser.RegexpString;
import javapop.framework.parser.Reserved;
import javapop.framework.parser.WhiteSpace;
import javapop.framework.parser.XForm;
import javapop.framework.parser.expr.ExprAssoc;
import javapop.framework.parser.expr.ExprNode;
import javapop.framework.parser.expr.Expression;
import javapop.framework.parser.tuple.Eight;
import javapop.framework.parser.tuple.Five;
import javapop.framework.parser.tuple.Four;
import javapop.framework.parser.tuple.ListTuple;
import javapop.framework.parser.tuple.Nine;
import javapop.framework.parser.tuple.One;
import javapop.framework.parser.tuple.Seven;
import javapop.framework.parser.tuple.Six;
import javapop.framework.parser.tuple.Ten;
import javapop.framework.parser.tuple.Three;
import javapop.framework.parser.tuple.Two;

/**
 * @author Hakim
 *
 */
@SuppressWarnings("rawtypes")
public class GrammarGenerator extends Grammar implements GrammarVisitor<Parser, ParseResult, GrammarVisitorException> {

	private static GrammarGenerator last = new GrammarGenerator();
	public static GrammarGenerator getLastGenerator() {
		return last;
	}


	private HashMap<String, String> properties;
	private List<String> imports;
	private String start;
	private Set<String> keywords;
	private HashMap<String, String> defrules;
	private HashMap<String, GParser> rules;
	private HashMap<String, Expression<ExprNode>> exprs;

	private HashMap<String, GExprOperator> operators = new HashMap<String, GExprOperator>();

	public GExprOperator getOperators(String symbol) {
		return operators.get(symbol);
	}

	private Object reflex;
	private Class cl;
	private boolean built;
	/**
	 * 
	 */
	public GrammarGenerator() {
		last = this;
		imports = new ArrayList<String>();
		properties = new HashMap<String, String>();
		keywords = new HashSet<String>();
		defrules = new HashMap<String, String>();
		rules = new HashMap<String, GParser>();
		exprs = new HashMap<String, Expression<ExprNode>>();

		fixSkipParser();
	}

	private void fixSkipParser() {
		Parser<Object> spaces = new Forget(new Many<Character>(
				WhiteSpace.getInstance()));
		register("spaces", spaces);

		Parser<Object> lineComment = new Forget(new LineComment(new Literal(
				"//")));
		register("lineComment", lineComment);

		Parser<Object> skipPart = new ForgetAll(ref("spaces"), new Maybe<Object>(
				ref("lineComment")));
		Parser<List<Object>> skip = new Many<Object>((Parser<Object>) skipPart);
		register("skip", skip);
		skip(ref("skip"));
	}

	@Override
	public Parser visit(GDefRegle r) throws GrammarVisitorException {
		defrules.put(r.getName(), r.getType());
		return null;
	}

	@Override
	public Parser visit(GKeyword o) throws GrammarVisitorException {
		keywords.add(o.getKeyword());
		return null;
	}

	@Override
	public Parser visit(GKeywords o) throws GrammarVisitorException {
		for (GKeyword keyword : o.getKeywords()) {
			keywords.add(keyword.getKeyword());
		}
		return null;
	}

	@Override
	public Parser visit(GChoice o) throws GrammarVisitorException {
		Choice choice = new Choice();
		boolean first = true;

		for (GParser parser : o.getChoices()) {
			Parser p = parser.visit(this);
			if(first) {
				choice.either(p);
				first = false;
			}
			else 
				choice.orElse(p);
		}
		return choice;
	}


	@Override
	public Parser<? extends ExprNode> visit(GExprBracket b) throws GrammarVisitorException {
		Expression<ExprNode> expression = exprs.get(b.getParser());
		if(expression == null) {
			throw new GrammarVisitorException("Bracket in an unknown Expression Parser");
		}
		expression.bracket(new Literal(b.getOpen()), new Literal(b.getClose()) );
		exprs.put(b.getParser(),expression);
		return expression;
	}

	@Override
	public Parser visit(GExprOperand o) throws GrammarVisitorException {
		Expression<ExprNode> expression = exprs.get(o.getParser());
		if(expression == null) {
			throw new GrammarVisitorException("Operand in an unknown Expression Parser");
		}

		Parser p = o.getOperand().visit(this);
		expression.operand(p);
		return expression;
	}

	@Override
	public Parser visit(final GExprOperator o) throws GrammarVisitorException {

		Parser parser =  o.getOperator().visit(this);
		
		Expression<ExprNode> expression = exprs.get(o.getParser());
		if(expression == null) {
			throw new GrammarVisitorException("Operator in an unknown Expression Parser");
		}
		switch(o.getType()) {
		case INFIX:
			expression.infix(o.getPrio(), convAssoc(o.getAssoc()), parser);
			break;
		case POSTFIX:
			expression.postfix(o.getPrio(), parser);
			break;
		case PREFIX:
			expression.prefix(o.getPrio(), parser);
			break;
		}
		return expression;
	}

	private ExprAssoc convAssoc(Assoc assoc) throws GrammarVisitorException {
		switch(assoc) {
		case LEFT:
			return ExprAssoc.LEFT;
		case NONE:
			return ExprAssoc.NO;
		case RIGHT:
			return ExprAssoc.RIGHT;
		}
		throw new GrammarVisitorException("unknown association: "+assoc.toString());
	}

	@Override
	public Parser visit(GExprParser o) throws GrammarVisitorException {
		Expression<ExprNode> expr = new Expression<ExprNode>(o.getName());
		expr.skip(ref("skip"));
		exprs.put(o.getName(), expr);
		return expr;
	}

	@Override
	public Parser visit(GIgnore o) throws GrammarVisitorException {
		return o.getParser().visit(this);
		//throw new GrammarVisitorException("Not yet supported Ignore parser (sorry)");
	}

	@Override
	public Parser visit(GRefParser o) throws GrammarVisitorException {
		return ref(o.getRef());
	}

	@Override
	public Parser visit(GRegexp o) throws GrammarVisitorException {
		return new RegexpString(o.getRegexp());
	}

	@Override
	public Parser<String> visit(GRegexpWithoutKeywords o)
			throws GrammarVisitorException {
		RegexpString reg =  new RegexpString(o.getRegexp());
		return new Reserved<String>(reg, keywords);
	}

	@Override
	public Parser visit(GListOf o) throws GrammarVisitorException {
		Parser parser = o.getParser().visit(this);
		ListOf listof = new ListOf(parser);
		One<String> one = new One<String>();
		one.then(ref("skip"));
		one.parser(literal(o.getSep()));
		one.then(ref("skip"));
		listof.sep(one);
		return listof;
	}

	@Override
	public Parser visit(GLiteral o) throws GrammarVisitorException {
		return literal(o.getLiteral());
	}

	@Override
	public Parser visit(final GMaybeOrDefault o) throws GrammarVisitorException {
		Parser child = o.getParser().visit(this);
		MaybeOrDefault mod = new MaybeOrDefault(child, null);
		@SuppressWarnings("unchecked")
		XForm xform = new XForm(mod) {
			@Override
			public Object transform(ParseContext ctx, Object content) {
				if(content == null)
					return callFonction(o.getFctDefault());
				else
					return content;
			}
		};
		return xform;
		//throw new NotImplementedException();
	}
	
	@Override
	public Parser visit(GMaybe g) throws GrammarVisitorException {
		Parser child = g.getParser().visit(this);
		Maybe mod = new Maybe(child);
		return mod;
	}
	

	@Override
	public Parser visit(final GTransform o) throws GrammarVisitorException {
		XForm xform = new XForm(o.getParser().visit(this)) {
			@Override
			public Object transform(ParseContext ctx, Object content) {
				return callFonction(o.getFct(), content);
			}
		};
		return xform;
	}

	private Object callFonction(String fct, Object content)  throws RuntimeException {
		try {
			//			for (Method m : cl.getMethods()) {
			//				if(fct.equals(m.getName())) {
			//					System.out.println(m.getName());
			//					return m.invoke(reflex, content);
			//				}
			//			}
			//throw new NoSuchMethodError(fct);
			Method meth = cl.getMethod(fct, Object.class);
			return meth.invoke(reflex, content);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private Object callFonction(String fct)  throws RuntimeException {
		try {
			Method meth = cl.getMethod(fct);
			return meth.invoke(reflex);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Parser visit(GTuple o) throws GrammarVisitorException {
		int count = 0;
		for (GParser parser : o.getList()) {
			if(! (parser instanceof GIgnore)) {
				count++;
			}
		}
		switch(count) {
		case 1:	return parseOne(o.getList());
		case 2: return parseTwo(o.getList());
		case 3: return parseThree(o.getList());
		case 4: return parseFour(o.getList());
		case 5: return parseFive(o.getList());
		case 6: return parseSix(o.getList());
		case 7: return parseSeven(o.getList());
		case 8: return parseEight(o.getList());
		case 9: return parseNine(o.getList());
		case 10: return parseTen(o.getList());
		default:
			ListTuple t = new ListTuple(count);
			for (GParser g : o.getList()) {
				if(g instanceof GIgnore)
					t.then(g.visit(this));
				else
					t.parser(g.visit(this));
				
			}
			return t;
			//throw new GrammarVisitorException("Arity of parser not supported: "+count);
		}

	}

	// ----------------------------------------------------------------------
	
	@SuppressWarnings("unchecked")
	private Parser parseTen(List<GParser> o) throws GrammarVisitorException {
		Ten two = new Ten();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				case 5: two.sixth(g.visit(this)); break;
				case 6: two.seventh(g.visit(this)); break;
				case 7: two.eighth(g.visit(this)); break;
				case 8: two.ninth(g.visit(this)); break;
				case 9: two.tenth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}
	
	@SuppressWarnings("unchecked")
	private Parser parseNine(List<GParser> o) throws GrammarVisitorException {
		Nine two = new Nine();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				case 5: two.sixth(g.visit(this)); break;
				case 6: two.seventh(g.visit(this)); break;
				case 7: two.eighth(g.visit(this)); break;
				case 8: two.ninth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}
	
	@SuppressWarnings("unchecked")
	private Parser parseEight(List<GParser> o) throws GrammarVisitorException {
		Eight two = new Eight();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				case 5: two.sixth(g.visit(this)); break;
				case 6: two.seventh(g.visit(this)); break;
				case 7: two.eighth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}
	
	private Parser parseSeven(List<GParser> o) throws GrammarVisitorException {
		Seven two = new Seven();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				case 5: two.sexth(g.visit(this)); break;
				case 6: two.seventh(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}
	
	
	private Parser parseSix(List<GParser> o) throws GrammarVisitorException {
		Six two = new Six();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				case 5: two.sixth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}

	private Parser parseFive(List<GParser> o) throws GrammarVisitorException {
		Five two = new Five();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				case 4: two.fifth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}

	private Parser parseFour(List<GParser> o) throws GrammarVisitorException {
		Four two = new Four();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				case 3: two.fourth(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}

	private Parser parseThree(List<GParser> o) throws GrammarVisitorException {
		Three two = new Three();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				case 2: two.third(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}

	private Parser parseTwo(List<GParser> o) throws GrammarVisitorException {
		Two two = new Two();
		int pos = 0;
		for (GParser g : o) {
			if(g instanceof GIgnore) {
				two.then(g.visit(this));
			}
			else {
				switch(pos) {
				case 0: two.first(g.visit(this)); break;
				case 1: two.second(g.visit(this)); break;
				default: throw new GrammarVisitorException("too many elements for tuple");
				}
				pos++;
			}
		}
		return two;
	}

	private Parser parseOne(List<GParser> list) throws GrammarVisitorException {
		One one = new One();
		for (GParser g : list) {
			if(g instanceof GIgnore)
				one.then(g.visit(this));
			else
				one.parser(g.visit(this));
		}
		return one;
	}

	@Override
	public Parser visit(GMeta o) throws GrammarVisitorException {
		if(o.getProperty().equals("grammar")) {
			fixGrammarConv(o.getValue());
		}

		if(o.getProperty().equals("import")) {
			imports.add(o.getValue());
		}
		else if(o.getProperty().equals("start"))
			this.start = o.getValue();
		else
			properties.put(o.getProperty(), o.getValue());
		return null;
	}

	private void fixGrammarConv(String value) throws GrammarVisitorException {
		try {
			cl = Class.forName(value);
			reflex = cl.newInstance();
		} catch (Exception e) {
			throw new GrammarVisitorException(e);
		}
	}

	@Override
	public Parser visit(GRegle o) throws GrammarVisitorException {
		this.defrules.put(o.getRegle().getName(), o.getRegle().getType());
		this.rules.put(o.getRegle().getName(),o.getParser());
		return null;
	}

	@Override
	public void init(List<GrammarTree> grammar) throws GrammarVisitorException {
		for (GrammarTree tree : grammar) {
			tree.visit(this);
		}
		// checkReferences(); // il ne gere pas les expressions
		buildGrammar();
	}

	public void buildGrammar() throws GrammarVisitorException {
		if(!built) {
			for (Entry<String, GParser> item : rules.entrySet()) {
				register(item.getKey(), item.getValue().visit(this));
			}
			for (Entry<String, Expression<ExprNode>> item : exprs.entrySet()) {
				register(item.getKey(), item.getValue());
			}
			setMainParser(ref(this.start));
			built = true;
		}

	}

	@Override
	public ParseResult<?> treatment(ParseInput input) throws GrammarVisitorException {
		buildGrammar();

		DefaultParseContext ctx = new DefaultParseContext();
		ParseResult<?> r = parse(ctx,input);
		if(r.isError())
			return ctx.deepest();
		else
			return r;
	}

	public ParseResult<?> parseTarget(String ref, ParseInput input) throws GrammarVisitorException {
		buildGrammar();
		DefaultParseContext ctx = new DefaultParseContext();
		Parser<Object> p = ref(ref);
		//Parser<?> seq = new One<Object>().parser(p).then(EOF.getInstance());
		ParseResult<?> r = p.parse(ctx,input);
		if(r.isError())
			return ctx.deepest();
		else
			return r;
	}

	@Override
	public ParseResult<?> parse(ParseInput input) {
		try {
			buildGrammar();
		} catch (GrammarVisitorException e) {
			e.printStackTrace();
			return new ParseError("Grammar build FAILED!!!!");
		}
		DefaultParseContext ctx = new DefaultParseContext();
		Parser<?> p = getMainParser();
		//Parser<?> seq = new One<Object>().parser(p).then(EOF.getInstance());
		ParseResult<?> r = p.parse(ctx,input);
		if(r.isError())
			return ctx.deepest();
		else
			return r;
	}
	
	// --------------------------
	
	private static String parseInputFromStream(InputStream stream) throws IOException {
		DataInputStream dis =  new DataInputStream(stream);
		StringBuilder sb = new StringBuilder();
		byte[] b = new byte[2048];
		while((dis.read(b)) >= 0) {
			sb.append(new String(b));
		}
		return sb.toString();
	}
	
	public static GrammarGenerator buildGrammarGenerator(InputStream grammar) throws GrammarGeneratorException {
		try {
		GenericGrammar generic = new GenericGrammar();
		DefaultParseContext ctx = new DefaultParseContext();
		ParseInput input = new StringParseInput(parseInputFromStream(grammar));
		ParseResult<?> obj = generic.parse(ctx,input);
		if(obj.isError()) {
			throw new GrammarGeneratorException(ctx.deepest().getDetailedErrorMessage());
		}
		else if(obj.isNull()) {
			throw new GrammarGeneratorException("Error null node on the grammar");
		}
		else {
			GrammarGenerator gen = new GrammarGenerator();
			@SuppressWarnings("unchecked")
			List<GrammarTree> result = (List<GrammarTree>) obj.getResult();
			gen.init(result);
			return gen;
		}
		}
		catch(Throwable e) {
			throw new GrammarGeneratorException(e);
		}
	}

}
